import 'package:casdon/core/utils/string_externtion.dart';
import 'package:casdon/core/utils/toast_utils.dart';
import 'package:dio/dio.dart';
import 'package:intl/intl.dart';

import 'package:casdon/modules/me/data/api/oss_sts_api.dart';
import 'package:casdon/modules/me/domain/usecase/oss_sts_usecase.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_oss_aliyun/flutter_oss_aliyun.dart';
import 'package:uuid/uuid.dart';

final ossTokenProvider =
    AsyncNotifierProvider<OssTokenNotifier, ResponseOssStsApi?>(
      () => OssTokenNotifier(),
    );

class OssTokenNotifier extends AsyncNotifier<ResponseOssStsApi?> {
  static const _imageDir = 'cloud-app/image';
  static const _videoDir = 'cloud-app/video';

  late Client client;
  @override
  Future<ResponseOssStsApi?> build() async {
    return getOSSStsToken();
  }

  Future<ResponseOssStsApi?> getOSSStsToken() async {
    final res = await ref.read(ossStsUsecaseProvider).execute();
    final bucketName = res.bucket;
    final endpoint = "${res.region}.aliyuncs.com";
    // final region = res.region; // region 不直接使用
    final accessKeyId = res.credentials.accessKeyId;
    final secretKeyId = res.credentials.accessKeySecret;
    final securityToken = res.credentials.securityToken;

    Auth authGetter() {
      return Auth(
        accessKey: accessKeyId,
        accessSecret: secretKeyId,
        secureToken: securityToken,
        expire: DateFormat(
          "yyyy-MM-dd'T'HH:mm:ss'Z'",
        ).format(DateTime.now().add(Duration(days: 365)).toUtc()),
      );
    }

    client = Client.init(
      ossEndpoint: endpoint,
      bucketName: bucketName,
      authGetter: authGetter,
    );

    state = AsyncData(res);
    return res;
  }

  // 返回ossId，支持进度回调
  Future<String?> uploadFile(
    String filePath, {
    String? source,
    ProgressCallback? onProgress,
  }) async {
    final uploadFileName = _generateUploadFileName(filePath, source: source);
    final entity = AssetFileEntity(
      filepath: filePath,
      filename: uploadFileName,
      option: PutRequestOption(
        // 若云端已存在相同 key，允许覆盖，避免 409 冲突
        override: true,
        onSendProgress: onProgress,
        aclModel: AclMode.publicRead,
        callback: Callback(
          callbackUrl: state.value!.callback.callbackUrl,
          callbackBody: state.value!.callback.callbackBody,
          callbackVar: {"uid": "casdon", "upload_file_name": uploadFileName},
          callbackSNI: state.value!.callback.callbackSNI,
          calbackBodyType: CalbackBodyType.json,
        ),
      ),
    );
    try {
      List<Response<dynamic>> resp = [];
      int attempts = 0;
      while (attempts < 3) {
        attempts++;
        try {
          resp = await client.putObjectFiles([entity]);
          break;
        } on DioException catch (e) {
          if ((e.message?.contains('Connection reset by peer') ?? false) &&
              attempts < 3) {
            final delayMs = 400 * attempts;
            await Future.delayed(Duration(milliseconds: delayMs));
            continue;
          }
          if (e.type == DioExceptionType.connectionError ||
              e.type == DioExceptionType.connectionTimeout ||
              e.type == DioExceptionType.sendTimeout ||
              e.type == DioExceptionType.receiveTimeout) {
            ToastUtil.show("网络连接断开了，请检查网络");
            return null;
          }
          rethrow;
        }
      }

      if (resp.isNotEmpty && resp.first.data is Map<String, dynamic>) {
        final data = resp.first.data as Map<String, dynamic>;
        final innerData = data["data"];
        if (innerData != null) {
          _logUploadLocation(uploadFileName);
          return innerData["ossId"];
        }
      }
      return null;
    } on DioException catch (e) {
      // 处理可能的 Token 过期等情况
      if (e.message?.contains("403") == true) {
        await getOSSStsToken();
        return uploadFile(filePath, source: source);
      }
      if (e.type == DioExceptionType.connectionError ||
          e.type == DioExceptionType.connectionTimeout ||
          e.type == DioExceptionType.sendTimeout ||
          e.type == DioExceptionType.receiveTimeout) {
        ToastUtil.show("网络连接断开了，请检查网络");
        return null;
      }
      rethrow;
    } catch (e) {
      print("OSS 错误: $e");

      rethrow;
    }
  }

  // 一次上传多个文件，返回每个文件对应的 ossId（与入参顺序一致）
  Future<List<String?>?> uploadFiles(
    List<String> filePaths, {
    String? source,
    ProgressCallback? onProgress,
  }) async {
    try {
      final List<AssetFileEntity> entities = [];
      final List<String> objectKeys = [];
      for (final filePath in filePaths) {
        final uploadFileName = _generateUploadFileName(
          filePath,
          source: source,
        );
        entities.add(
          AssetFileEntity(
            filepath: filePath,
            filename: uploadFileName,
            option: PutRequestOption(
              // 若云端已存在相同 key，允许覆盖，避免 409 冲突
              override: true,
              onSendProgress: onProgress,
              aclModel: AclMode.publicRead,
              callback: Callback(
                callbackUrl: state.value!.callback.callbackUrl,
                callbackBody: state.value!.callback.callbackBody,
                callbackVar: {
                  "uid": "casdon",
                  "upload_file_name": uploadFileName,
                },
                callbackSNI: state.value!.callback.callbackSNI,
                calbackBodyType: CalbackBodyType.json,
              ),
            ),
          ),
        );
        objectKeys.add(uploadFileName);
      }

      // 简单重试机制，处理网络瞬断（如 connection reset by peer）
      List<Response<dynamic>> resp = [];
      int attempts = 0;
      while (attempts < 3) {
        attempts++;
        try {
          resp = await client.putObjectFiles(entities);
          break;
        } on DioException catch (e) {
          if ((e.message?.contains('Connection reset by peer') ?? false) &&
              attempts < 3) {
            final delayMs = 400 * attempts;
            await Future.delayed(Duration(milliseconds: delayMs));
            continue;
          } else {
            if (e.type == DioExceptionType.connectionError ||
                e.type == DioExceptionType.connectionTimeout ||
                e.type == DioExceptionType.sendTimeout ||
                e.type == DioExceptionType.receiveTimeout) {
              ToastUtil.show("网络连接断开了，请检查网络");
              return null;
            }
          }
          rethrow;
        }
      }

      // 将返回的每一项解析出对应的 ossId，保持顺序
      final List<String?> ossIds = [];
      for (var i = 0; i < resp.length; i++) {
        final r = resp[i];
        if (r.data is Map<String, dynamic>) {
          final data = r.data as Map<String, dynamic>;
          final innerData = data["data"];
          if (innerData != null) {
            if (i < objectKeys.length) {
              _logUploadLocation(objectKeys[i]);
            }
            ossIds.add(innerData["ossId"] as String?);
          } else {
            ossIds.add(null);
          }
        } else {
          ossIds.add(null);
        }
      }
      return ossIds;
    } on DioException catch (e) {
      // 处理可能的 Token 过期等情况
      if (e.message?.contains("403") == true) {
        await getOSSStsToken();
        return uploadFiles(filePaths, source: source);
      }
      if (e.type == DioExceptionType.connectionError ||
          e.type == DioExceptionType.connectionTimeout ||
          e.type == DioExceptionType.sendTimeout ||
          e.type == DioExceptionType.receiveTimeout) {
        ToastUtil.show("网络连接断开了，请检查网络");
        return null;
      }
      rethrow;
    } catch (e) {
      print("OSS 多文件上传错误: $e");
      rethrow;
    }
  }

  // 返回imageUrl
  Future<String?> uploadImageForUrl(String filePath) async {
    final uploadFileName = _generateUploadFileName(filePath);
    try {
      final List<Response<dynamic>> resp = await client.putObjectFiles([
        AssetFileEntity(
          filepath: filePath,
          filename: uploadFileName,
          option: PutRequestOption(
            // 若云端已存在相同 key，允许覆盖，避免 409 冲突
            override: true,
            aclModel: AclMode.publicRead,
            callback: Callback(
              callbackUrl: state.value!.callback.callbackUrl,
              callbackBody: state.value!.callback.callbackBody,
              callbackVar: {
                "uid": "casdon",
                "upload_file_name": uploadFileName,
              },
              callbackSNI: state.value!.callback.callbackSNI,
              calbackBodyType: CalbackBodyType.json,
            ),
          ),
        ),
      ]);

      if (resp.isNotEmpty && resp.first.data is Map<String, dynamic>) {
        final data = resp.first.data as Map<String, dynamic>;
        final innerData = data["data"];
        if (innerData != null) {
          _logUploadLocation(uploadFileName);
          return innerData["url"];
        }
      }
      return null;
    } on DioException catch (e) {
      print("OSS 错误: $e");
      // 检查是否是过期导致的 403
      if ((e.message as String).contains("403")) {
        print("🔄 Token 过期，正在刷新...");
        await getOSSStsToken();
        return uploadImageForUrl(filePath);
      }
      rethrow;
    }
  }

  // 可提供一个方法供后续逻辑直接获取 Response
  ResponseOssStsApi? get token => state.value;

  void _logUploadLocation(String objectKey) {
    final token = state.value;
    final bucket = token?.bucket;
    final region = token?.region;
    if (bucket == null || region == null) {
      print("OSS 上传成功: key=$objectKey");
      return;
    }
    final endpoint = "$region.aliyuncs.com";
    final url = "https://$bucket.$endpoint/$objectKey";
    print("OSS 上传成功: key=$objectKey, url=$url");
  }

  String _generateUploadFileName(String filePath, {String? source}) {
    final ext = filePath.extention()!.toLowerCase();
    final uuid = Uuid().v4().replaceAll(RegExp(r'-'), '');
    final dir = filePath.isImagePath() ? _imageDir : _resolveVideoDir(source);
    return "$dir/$uuid$ext";
  }

  String _resolveVideoDir(String? source) {
    final normalized = source?.toLowerCase();
    if (normalized == 'topic') {
      return '$_videoDir/topic';
    }
    return '$_videoDir/feedback';
  }
}
